﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.IO;
using System.Data.Common;
using Renci.iRods.Linq.Translators;
using Renci.iRods.Linq.Common;

namespace Renci.iRods.Linq
{
    public class IRodsQueryProvider : QueryProvider
    {
        IRodsConnection _connection;

        QueryPolicy policy;
        QueryMapping mapping;
        QueryLanguage language;
        TextWriter log;

        public IRodsQueryProvider(IRodsConnection connection)
        {
            this._connection = connection;
            //this.policy = new QueryPolicy(new ImplicitMapping(connection, new TSqlLanguage()));
            this.policy = new QueryPolicy(new ImplicitMapping(connection, new PgSqlLanguage()));
            this.mapping = policy.Mapping;
            this.language = mapping.Language;
            this.log = Console.Error;
            
        }

        public IRodsConnection Connection
        {
            get { return this._connection; }
        }

        public TextWriter Log
        {
            get { return this.log; }
        }

        public QueryPolicy Policy
        {
            get { return this.policy; }
        }

        public QueryMapping Mapping
        {
            get { return this.mapping; }
        }

        public QueryLanguage Language
        {
            get { return this.language; }
        }

        /// <summary>
        /// Execute the query expression (does translation, etc.)
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public override object Execute(Expression expression)
        {
            Expression plan = this.GetExecutionPlan(expression);

            LambdaExpression lambda = expression as LambdaExpression;
            if (lambda != null)
            {
                // compile & return the execution plan so it can be used multiple times
                LambdaExpression fn = Expression.Lambda(lambda.Type, plan, lambda.Parameters);
                return fn.Compile();
            }
            else
            {
                // compile the execution plan and invoke it
                Expression<Func<object>> efn = Expression.Lambda<Func<object>>(Expression.Convert(plan, typeof(object)));
                Func<object> fn = efn.Compile();
                return fn();
            }
        }

        public virtual IEnumerable<T> Execute<T>(IRodsQuery query, Func<IRodsDataReader, T> fnProjector)
        {
            var reader = this._connection.ExecuteQuery(query);
            return Project(reader, fnProjector);
        }

        /// <summary>
        /// Convert the query expression into an execution plan
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected virtual Expression GetExecutionPlan(Expression expression)
        {
            // strip off lambda for now
            LambdaExpression lambda = expression as LambdaExpression;
            if (lambda != null)
                expression = lambda.Body;

            // translate query into client & server parts
            Expression translation = this.Translate(expression);

            Expression provider = TypedSubtreeFinder.Find(expression, typeof(IRodsQueryProvider));
            if (provider == null)
            {
                Expression rootQueryable = TypedSubtreeFinder.Find(expression, typeof(IQueryable));
                provider = Expression.Convert(
                    Expression.Property(rootQueryable, typeof(IQueryable).GetProperty("Provider")),
                    typeof(IRodsQueryProvider)
                    );
            }

            return this.policy.BuildExecutionPlan(translation, provider);
        }

        /// <summary>
        /// Do all query translations except building the execution plan
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected virtual Expression Translate(Expression expression)
        {
            // pre-evaluate local sub-trees
            expression = PartialEvaluator.Eval(expression, this.CanBeEvaluatedLocally);

            // apply mapping (binds LINQ operators too)
            expression = this.mapping.Translate(expression);

            // any policy specific translations or validations
            expression = this.policy.Translate(expression);

            // any language specific translations or validations
            expression = this.language.Translate(expression);

            // do final reduction
            expression = UnusedColumnRemover.Remove(expression);
            expression = RedundantSubqueryRemover.Remove(expression);
            expression = RedundantJoinRemover.Remove(expression);
            expression = RedundantColumnRemover.Remove(expression);

            return expression;
        }

        /// <summary>
        /// Determines whether a given expression can be executed locally. 
        /// (It contains no parts that should be translated to the target environment.)
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected virtual bool CanBeEvaluatedLocally(Expression expression)
        {
            // any operation on a query can't be done locally
            ConstantExpression cex = expression as ConstantExpression;
            if (cex != null)
            {
                IQueryable query = cex.Value as IQueryable;
                if (query != null && query.Provider == this)
                    return false;
            }
            MethodCallExpression mc = expression as MethodCallExpression;
            if (mc != null &&
                (mc.Method.DeclaringType == typeof(Enumerable) ||
                 mc.Method.DeclaringType == typeof(Queryable) ||
                 mc.Method.DeclaringType == typeof(Updatable))
                 )
            {
                return false;
            }
            if (expression.NodeType == ExpressionType.Convert &&
                expression.Type == typeof(object))
                return true;
            return expression.NodeType != ExpressionType.Parameter &&
                   expression.NodeType != ExpressionType.Lambda;
        }

        /// <summary>
        /// Converts a data reader into a sequence of objects using a projector function on each row
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="reader"></param>
        /// <param name="fnProject"></param>
        /// <returns></returns>
        public virtual IEnumerable<T> Project<T>(IRodsDataReader reader, Func<IRodsDataReader, T> fnProjector)
        {
            while (reader.Read())
            {
                yield return fnProjector(reader);
            }
        }

        protected virtual void LogMessage(string message)
        {
            if (this.log != null)
            {
                this.log.WriteLine(message);
            }
        }
    }
}
